/**
 * 
 */
package jforex.advanced;

import java.util.ArrayList;
import java.util.List;

import jforex.api.IBox;
import jforex.impl.JFToolBox;

import com.dukascopy.api.Configurable;
import com.dukascopy.api.IAccount;
import com.dukascopy.api.IBar;
import com.dukascopy.api.IChart;
import com.dukascopy.api.IConsole;
import com.dukascopy.api.IContext;
import com.dukascopy.api.IEngine;
import com.dukascopy.api.IHistory;
import com.dukascopy.api.IIndicators;
import com.dukascopy.api.IMessage;
import com.dukascopy.api.IOrder;
import com.dukascopy.api.IStrategy;
import com.dukascopy.api.ITick;
import com.dukascopy.api.Instrument;
import com.dukascopy.api.JFException;
import com.dukascopy.api.Period;

/**
 *
 */
public class StateMachineStrategy implements IStrategy {

    @Configurable("Instrument")
    public Instrument instrument = Instrument.EURUSD;

    @Configurable("Magic number")
    public int magic = 1127;

    @Configurable("Verbose")
    public boolean verbose = true;

    // global objects

    // variables
    protected ITick tick = null;
    protected double exposure = 0;
    protected double equity = 0;
    private List<IOrder> entrys = new ArrayList<IOrder>();
    private List<IOrder> positions = new ArrayList<IOrder>();
    protected IContext context = null;
    protected IHistory history = null;
    protected IIndicators indicators = null;
    protected IEngine engine = null;
    protected IConsole console = null;
    protected IChart chart = null;
    protected IBox box = null;

    public void onStart(IContext context) throws JFException {

        this.context = context;
        this.engine = context.getEngine();
        this.history = context.getHistory();
        this.indicators = context.getIndicators();
        this.chart = context.getChart(instrument);
        this.box = new JFToolBox(context);

    }

    public void onAccount(IAccount account) throws JFException {
        this.equity = account.getEquity();
    }

    @Override
    public void onBar(Instrument instrument, Period period, IBar askBar, IBar bidBar) throws JFException {

    }

    @Override
    public void onTick(Instrument instr, ITick tickOnTick) throws JFException {
        if (instr != instrument) {
            return;
        }
        if (equity == 0) {
            log("Waiting for account update.");
            return;
        }
        processStates(tickOnTick);

    }
    protected void processStates() throws JFException {
        processStates(null);
    }
    protected void processStates(ITick tickOnTick) throws JFException {
        boolean canExitForNextTick = false;
        int loopAlert = 10;
        while (!canExitForNextTick && loopAlert > 0) {
            loopAlert--;
            boolean haveCreatedState = false;
            entrys.clear();
            positions.clear();
            exposure = 0;
            for (IOrder order : engine.getOrders(instrument)) {
                if (order.getState() == IOrder.State.OPENED && box.getMagicNumber(order) == magic) {
                    entrys.add(order);
                }
                if (order.getState() == IOrder.State.FILLED && box.getMagicNumber(order) == magic) {
                    positions.add(order);

                    double amo = order.getAmount();
                    if (order.isLong()) {
                        exposure = exposure + amo;
                    } else {
                        exposure = exposure - amo;
                    }
                }
                if (order.getState() == IOrder.State.CREATED) {
                    haveCreatedState = true;
                }
            }
            if (haveCreatedState) {
                log("Detected order in created state. Waiting.");
                return;
            }
            exposure = box.round(exposure, 2);

            ITick hTick = history.getLastTick(instrument);
            if (hTick.getTime() > tickOnTick.getTime()) {
                tick = hTick;
            } else {
                tick = tickOnTick;
            }

            if (!preProcess(positions, entrys, exposure)) {
                return;
            }

            if (positions.size() == 0) {
                if (entrys.size() == 0) {
                    canExitForNextTick = state00();
                } else if (entrys.size() == 1) {
                    canExitForNextTick = state01(entrys.get(0));
                } else {
                    canExitForNextTick = state0M(entrys);
                }
            } else if (positions.size() == 1) {
                if (entrys.size() == 0) {
                    canExitForNextTick = state10(positions.get(0));
                } else if (entrys.size() == 1) {
                    canExitForNextTick = state11(positions.get(0), entrys.get(0));
                } else {
                    canExitForNextTick = state1M(positions.get(0), entrys);
                }
            } else {
                if (entrys.size() == 0) {
                    canExitForNextTick = stateM0(positions);

                } else if (entrys.size() == 1) {
                    canExitForNextTick = stateM1(positions, entrys.get(0));

                } else {
                    canExitForNextTick = stateMM(positions, entrys);
                }

            }
        }
        if (loopAlert <= 0) {
            log("STRATEGY STOPPED BY LOOP ALERT.");
            context.stop();
        }

    }

    // ----------------------------------------------------------------------
    protected void log(Object... str) {
        if (verbose) {
            box.print(str);
        }
    }

    public boolean preProcess(List<IOrder> positions, List<IOrder> entrys, double exposure) {
        return true;
    }

    /**
     * State indicating that no positions and no orders for current magic number and instrument was found
     * 
     * @return true if state machine can process next ticks, false if need immediately recalculate states and process
     * @throws JFException
     */
    public boolean state00() throws JFException {
        log("STATE NOT OVERLOADED state00");
        return true;
    }

    /**
     * no positions but one order
     * 
     * @param order
     * @return true if state machine can process next ticks, false if need immediately recalculate states and process
     * @throws JFException
     */
    public boolean state01(IOrder order) throws JFException {
        log("STATE NOT OVERLOADED state01");
        return true;
    }

    /**
     * no positions but more then one order
     * 
     * @param entrys
     * @return true if state machine can process next ticks, false if need immediately recalculate states and process
     * @throws JFException
     */
    public boolean state0M(List<IOrder> orders) throws JFException {
        log("STATE NOT OVERLOADED state0M");
        return true;
    }

    /**
     * 
     * @param position
     * @return true if state machine can process next ticks, false if need immediately recalculate states and process
     * @throws JFException
     */
    public boolean state10(IOrder position) throws JFException {
        log("STATE NOT OVERLOADED state10");
        return true;
    }

    /**
     * one position and one order
     * 
     * @param position
     * @param entry
     * @return true if state machine can process next ticks, false if need immediately recalculate states and process 
     * @throws JFException
     */
    public boolean state11(IOrder position, IOrder order) throws JFException {
        log("STATE NOT OVERLOADED state11");
        return true;
    }

    /**
     * one position and more then one order
     * 
     * @param position
     * @param entrys
     * @return true if state machine can process next ticks, false if need immediately recalculate states and process 
     * @throws JFException
     */
    public boolean state1M(IOrder position, List<IOrder> orders) throws JFException {
        log("STATE NOT OVERLOADED state1M");
        return true;
    }

    /**
     * many positions and no orders
     * 
     * @param positions
     * @return true if state machine can process next ticks, false if need immediately recalculate states and process
     * @throws JFException
     */
    public boolean stateM0(List<IOrder> positions) throws JFException {
        log("STATE NOT OVERLOADED stateM0");
        return true;
    }

    /**
     * many positions and one order
     * @param positions
     * @param order
     * @return true if state machine can process next ticks, false if need immediately recalculate states and process
     * @throws JFException
     */
    public boolean stateM1(List<IOrder> positions, IOrder order) throws JFException {
        log("STATE NOT OVERLOADED stateM1");
        return true;
    }

    /**
     * many positions and many orders
     * 
     * @param positions
     * @param entrys
     * @return true if state machine can process next ticks, false if need immediately recalculate states and process
     * @throws JFException
     */
    public boolean stateMM(List<IOrder> positions, List<IOrder> orders) throws JFException {
        log("STATE NOT OVERLOADED stateMM");
        return true;
    }

    @Override
    public void onMessage(IMessage message) throws JFException {
       if(verbose){
           log("message received for "+getClass().getSimpleName()+" "+message);
       }

    }

    @Override
    public void onStop() throws JFException {
        // may be implemented in child if needed

    }

}
